/* Copyright(C) 2006-2008 Dave Sexton  *
 * http://www.codeplex.com/aip         *
 * http://www.codeplex.com/aip/license *
 ***************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace DaveSexton.Web.Controls
{
	/// <summary>
	/// An implementation of <see cref="AutoInputProtectionImage"/> that derives from <see cref="PartitionedAutoInputProtectionImage"/> 
	/// and renders randomized line segments on a white surface image.
	/// </summary>
	/// <remarks>
	/// Instances of <see cref="LineNoiseAutoInputProtectionImage"/> are created by <see cref="LineNoiseAutoInputProtectionImageProvider"/> 
	/// and the <see cref="Colors"/> list is initialized to the values in the <see cref="LineNoiseAutoInputProtectionImageProvider.Colors"/>
	/// list.
	/// </remarks>
	/// <include file='examples.xml' path='//example[@id="LineNoise"][@type="images"]'/>
	/// <seealso cref="PartitionedAutoInputProtectionImage"/>
	/// <seealso cref="LineNoiseAutoInputProtectionImageProvider"/>
	public sealed class LineNoiseAutoInputProtectionImage : PartitionedAutoInputProtectionImage
	{
		#region Public Properties
		/// <summary>
		/// Gets a list of <see cref="Color">Colors</see> from which one will be sampled at random and used to draw a single 
		/// randomized line segment.
		/// </summary>
		/// <remarks>
		/// <para>
		/// At least one <see cref="Color"/> must be specified or else the <see cref="CreateSurfaceImage"/> method will throw 
		/// an <see cref="InvalidOperationException"/>.
		/// </para>
		/// <para>
		/// By default, <see cref="Color.Black"/> is added to the list if no colors are specified when an instance of this 
		/// class is constructed.
		/// </para>
		/// </remarks>
		/// <value>List of <see cref="Color">Colors</see> from which one will be sampled at random and used to draw a single 
		/// randomized line segment.</value>
		public IList<Color> Colors
		{
			get
			{
				return colors;
			}
		}
		#endregion

		#region Private / Protected
		private readonly IList<Color> colors;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="LineNoiseAutoInputProtectionImage" /> class.
		/// </summary>
		/// <remarks>
		/// This constructor adds <see cref="Color.Black"/> to the <see cref="Colors"/> list.
		/// </remarks>
		/// <exception cref="ArgumentNullException">The specified <paramref name="textProvider"/> is <see langword="null"/>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="size"/> is less than the value of 
		/// <see cref="AutoInputProtectionImage.MinimumSize"/>.</exception>
		/// <param name="size">The dimensions of the <see cref="AutoInputProtectionImage.CompositeImage"/>.</param>
		/// <param name="textProvider">The <see cref="AutoInputProtectionTextProvider"/> implementation that generates 
		/// the <see cref="AutoInputProtectionImage.Text"/> for the <see cref="AutoInputProtectionImage.CompositeImage"/>.</param>
		public LineNoiseAutoInputProtectionImage(Size size, AutoInputProtectionTextProvider textProvider)
			: base(size, textProvider)
		{
			colors = new List<Color>(1);
			colors.Add(Color.Black);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="LineNoiseAutoInputProtectionImage" /> class.
		/// </summary>
		/// <remarks>
		/// This constructor adds <see cref="Color.Black"/> to the <see cref="Colors"/> list if <paramref name="colors"/> is 
		/// <paramref name="null"/> or emtpy.
		/// </remarks>
		/// <exception cref="ArgumentNullException">The specified <paramref name="textProvider"/> is <see langword="null"/>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="size"/> is less than the value of 
		/// <see cref="AutoInputProtectionImage.MinimumSize"/>.</exception>
		/// <param name="size">The dimensions of the <see cref="AutoInputProtectionImage.CompositeImage"/>.</param>
		/// <param name="textProvider">The <see cref="AutoInputProtectionTextProvider"/> implementation that generates 
		/// the <see cref="AutoInputProtectionImage.Text"/> for the <see cref="AutoInputProtectionImage.CompositeImage"/>.</param>
		/// <param name="colors">An <see cref="IEnumerable{T}"/> of <see cref="Color">Colors</see> from which one will be sampled at random 
		/// for each random line segment that is generated.</param>
		public LineNoiseAutoInputProtectionImage(Size size, AutoInputProtectionTextProvider textProvider, IEnumerable<Color> colors)
			: base(size, textProvider)
		{
			if (colors == null)
				this.colors = new List<Color>(1);
			else
				this.colors = new List<Color>(colors);

			if (this.colors.Count == 0)
				this.colors.Add(Color.Black);
		}
		#endregion

		#region Methods
		/// <summary>
		/// Returns an <see cref="Image"/> of the specified <paramref name="size"/> to be used as the surface on which the 
		/// <see cref="AutoInputProtectionImage.CompositeImage"/> will be generated.
		/// </summary>
		/// <remarks>
		/// <para>
		/// The image that is generated consists of a white background and random line segments rendered with colors that are sampled 
		/// from the <see cref="Colors"/> list, one per line segment.
		/// </para>
		/// <para>
		/// See <see cref="LineNoiseAutoInputProtectionImage"/> for examples.
		/// </para>
		/// </remarks>
		/// <exception cref="InvalidOperationException">The <see cref="Colors"/> list is empty.</exception>
		/// <param name="size">The dimensions of the image.</param>
		/// <returns>An <see cref="Image"/> of the specified <paramref name="size"/> containing randomized line segments.</returns>
		protected override Image CreateSurfaceImage(Size size)
		{
			if (colors.Count == 0)
				throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentUICulture,
					Resources.Errors.NoColorsForLineNoise));

			Bitmap bitmap = new Bitmap(size.Width, size.Height);

			Random rnd = new Random();

			using (Graphics graphics = Graphics.FromImage(bitmap))
			{
				graphics.FillRectangle(Brushes.White, graphics.VisibleClipBounds);

				using (GraphicsPath path = new GraphicsPath())
				{
					int width = size.Width - Margin;
					int height = size.Height - Margin;
					int minPosition = Margin + 5;
					int n = 0;
					Point end = Point.Empty;
					bool hasDrawnPath = false;

					using (Pen pen1 = new Pen(colors[rnd.Next(0, colors.Count)], 1))
					{
						using (Pen pen2 = new Pen(colors[rnd.Next(0, colors.Count)], 2))
						{
							path.AddBezier(
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))),
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))),
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))),
								end = new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))));

							n = rnd.Next(0, 11);

							if (n < 6)
							{
								graphics.DrawPath((n > 2) ? pen1 : pen2, path);
								path.Reset();
								hasDrawnPath = true;
							}

							for (int i = 0; i < rnd.Next(0, 4); i++)
							{
								Point start = end;

								end = new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height)));

								path.AddLine(start, end);

								n = rnd.Next(0, 11);

								if (n < 6)
								{
									graphics.DrawPath((n > 2) ? pen1 : pen2, path);
									path.Reset();
									hasDrawnPath = true;
								}
							}

							path.AddBezier(end,
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))),
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))),
								new Point(rnd.Next(Margin, Math.Max(minPosition, width)),
									rnd.Next(Margin, Math.Max(minPosition, height))));

							n = rnd.Next(0, 11);

							if (!hasDrawnPath || n < 6)
								graphics.DrawPath((n > 2) ? pen1 : pen2, path);
						}
					}
				}
			}

			return bitmap;
		}
		#endregion
	}
}
